﻿using System;

namespace HART.Messages
{
    /// <summary>
    /// 实现从从设备发送到主设备的响应消息。
    /// </summary>
    public class Response : MessageBase
    {
        /// <summary>
        /// 响应码。
        /// </summary>
        public int ResponseCode { get; private set; }

        /// <summary>
        /// 通信错误描述。
        /// </summary>
        public string CommunicationError { get; private set; }

        /// <summary>
        /// 如果设备处于批量数据传输模式，则为 <see langword="true"/>。
        /// </summary>
        public bool IsBatchMode { get; private set; }

        /// <summary>
        /// 创建新的响应消息。
        /// </summary>
        /// <param name="address">主设备地址。</param>
        private Response(byte[] address) : base(address)
        {
        }

        /// <summary>
        /// 反序列化响应。
        /// </summary>
        /// <param name="buffer">用于反序列化的数据。</param>
        /// <exception cref="ArgumentException">数据未通过奇偶校验。校验和不匹配。</exception>
        public static Response Deserialize(byte[] buffer)
        {
            var offset = 0;

            var preamble = SetPreamble(buffer);
            offset += preamble;

            if (!OddCheck(buffer, offset))
                throw new ArgumentException("数据未通过奇偶校验。校验和不匹配。");

            var limiter = SetLimiter(buffer, offset);
            var frameFormat = SetFrameFormat(limiter);
            var isBatchMode = SetIsBatchMode(limiter);
            offset++;

            var address = SetAddress(buffer, offset, frameFormat);
            offset += address.Length;

            var command = SetCommand(buffer, offset);
            offset += command.Length;

            //var counter = buffer[offset];
            offset++;

            var code = SetCode(buffer, offset);
            offset += code.Length;

            var data = SetData(buffer, offset);

            return new Response(address)
            {
                Preamble = preamble,
                Limiter = limiter,
                FrameFormat = frameFormat,
                IsBatchMode = isBatchMode,
                CommunicationError = CommunicationErrorList.GetErrorDescription(code[0]),
                ResponseCode = Convert.FromByte<int>(code),
                Data = data
            };
        }

        #region private

        /// <summary>
        /// 获取前导码的字符数。
        /// </summary>
        /// <param name="data">用于反序列化的数据。</param>
        /// <returns>前导码的字符数。</returns>
        private static int SetPreamble(byte[] data)
        {
            var count = 0;
            var isStart = false;

            foreach (var item in data)
            {
                if (item == PreambleSymbol)
                {
                    isStart = true;
                    count++;
                }

                else if (isStart) break;
            }

            return count;
        }

        /// <summary>
        /// 检查接收到的数据。
        /// </summary>
        /// <param name="data">用于反序列化的数据。</param>
        /// <param name="offset">从 <paramref name="data"/> 数组中开始读取数据的偏移量。</param>
        /// <returns>如果校验成功，则返回 <see langword="true"/>。</returns>
        private static bool OddCheck(byte[] data, int offset)
        {
            var expected = data[data.Length - 1];
            byte actual = 0;

            for (var i = offset; i < data.Length - 2; i++)
                actual ^= data[i];

            return expected == actual;
        }

        /// <summary>
        /// 获取限制符。
        /// </summary>
        /// <param name="data">用于反序列化的数据。</param>
        /// <param name="offset">从 <paramref name="data"/> 数组中开始读取数据的偏移量。</param>
        /// <returns>限制符。</returns>
        private static byte SetLimiter(byte[] data, int offset)
            => data[offset];

        /// <summary>
        /// 获取帧格式。
        /// </summary>
        /// <param name="limiter">限制符。</param>
        /// <returns>帧格式。</returns>
        private static FrameFormats SetFrameFormat(byte limiter)
        {
            switch (limiter)
            {
                case 0x81:
                case 0x82:
                case 0x86: return FrameFormats.Long;

                case 0x01:
                case 0x02:
                case 0x06: return FrameFormats.Short;

                default: throw new ArgumentException("无法识别帧格式。");
            }
        }

        /// <summary>
        /// 获取设备的当前数据传输模式。
        /// </summary>
        /// <param name="limiter">限制符。</param>
        /// <returns>如果设备处于批量数据传输模式，则返回 <see langword="true"/>。</returns>
        private static bool SetIsBatchMode(byte limiter) =>
            limiter == 0x01 || limiter == 0x81;

        /// <summary>
        /// 获取发送响应的从设备地址。
        /// </summary>
        /// <param name="data">用于反序列化的数据。</param>
        /// <param name="offset">从 <paramref name="data"/> 数组中开始读取数据的偏移量。</param>
        /// <param name="frameFormat">帧格式。</param>
        /// <returns>从设备地址。</returns>
        private static byte[] SetAddress(byte[] data, int offset, FrameFormats frameFormat)
        {
            byte[] result = null;

            switch (frameFormat)
            {
                case FrameFormats.Short:
                    result = new byte[1];
                    result[0] = data[offset];
                    break;
                case FrameFormats.Long:
                    result = new byte[5];
                    for (var i = 0; i < 5; i++)
                        result[i] = data[i + offset];
                    break;
            }

            return result;
        }

        /// <summary>
        /// 获取传输的命令号。
        /// </summary>
        /// <param name="data">用于反序列化的数据。</param>
        /// <param name="offset">从 <paramref name="data"/> 数组中开始读取数据的偏移量。</param>
        /// <returns>命令号。</returns>
        private static byte[] SetCommand(byte[] data, int offset)
        {
            byte[] result;

            if (data[offset] == 254)
                result = new[]
                {
                        data[offset],
                        data[offset + 1]
                    };
            else
                result = new[]
                {
                        data[offset]
                    };

            return result;
        }

        /// <summary>
        /// 获取响应码。
        /// </summary>
        /// <param name="data">用于反序列化的数据。</param>
        /// <param name="offset">从 <paramref name="data"/> 数组中开始读取数据的偏移量。</param>
        /// <returns>响应码。</returns>
        private static byte[] SetCode(byte[] data, int offset) => new[]
            {
                    data[offset],
                    data[offset + 1]
                };

        /// <summary>
        /// 获取从设备响应中的数据。
        /// </summary>
        /// <param name="data">用于反序列化的数据。</param>
        /// <param name="offset">从 <paramref name="data"/> 数组中开始读取数据的偏移量。</param>
        /// <returns>数据。</returns>
        private static byte[] SetData(byte[] data, int offset)
        {
            var count = data.Length - offset - 1;
            var result = new byte[count];

            for (var i = 0; i < count; i++)
                result[i] = data[offset + i];

            return result;
        }

        #endregion
    }
}
